package org.elasticsearch.plugin.analysis.ik;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.aggregate.InternalTimeLimiting;
import org.elasticsearch.aggregate.TimeLimitingAggregationBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.analysis.AnalyzerProvider;
import org.elasticsearch.index.analysis.IkAnalyzerProvider;
import org.elasticsearch.index.analysis.IkTokenizerFactory;
import org.elasticsearch.index.analysis.TokenizerFactory;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.indices.analysis.AnalysisModule;
import org.elasticsearch.mapper.PreAnalyzedMapper;
import org.elasticsearch.plugins.*;
import org.elasticsearch.rescore.Config;
import org.elasticsearch.rescore.GroupingMixupRescorerBuilder;
import org.elasticsearch.rescore.RedisRescoreBuilder;
import org.elasticsearch.script.PositionRecipScriptEngine;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptEngine;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.PayloadSearchQueryBuilder;
import org.elasticsearch.search.fetch.FetchSubPhase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import redis.clients.jedis.Jedis;

import java.nio.file.Path;
import java.util.*;

import static java.util.Collections.singletonList;
import static org.elasticsearch.rescore.Config.REDIS_URL;


public class AnalysisIkPlugin extends Plugin implements AnalysisPlugin, ActionPlugin, SearchPlugin, ScriptPlugin, MapperPlugin {

private final static Logger logger = LogManager.getLogger(IkTokenizerFactory.class);
	public static String PLUGIN_NAME = "analysis-ik";
    private final Config config;
    public AnalysisIkPlugin(final Settings settings, final Path configPath) {
        this.config = new Config(settings);
        RedisRescoreBuilder.setJedis(new Jedis(config.getRedisUrl()));
        // 已经拿到配置文件中的值了
        logger.warn("have finished plugin: " + config.getRedisUrl());
    }

    @Override
    public Map<String, AnalysisModule.AnalysisProvider<TokenizerFactory>> getTokenizers() {
        Map<String, AnalysisModule.AnalysisProvider<TokenizerFactory>> extra = new HashMap<>();


        extra.put("ik_smart", IkTokenizerFactory::getIkSmartTokenizerFactory);
        extra.put("ik_max_word", IkTokenizerFactory::getIkTokenizerFactory);

        return extra;
    }

    @Override
    public Map<String, AnalysisModule.AnalysisProvider<AnalyzerProvider<? extends Analyzer>>> getAnalyzers() {
        Map<String, AnalysisModule.AnalysisProvider<AnalyzerProvider<? extends Analyzer>>> extra = new HashMap<>();

        extra.put("ik_smart", IkAnalyzerProvider::getIkSmartAnalyzerProvider);
        extra.put("ik_max_word", IkAnalyzerProvider::getIkAnalyzerProvider);

        return extra;
    }
@Override
    public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
        clusterService.addListener(event -> {
            for (Index index : event.indicesDeleted()) {
                logger.warn("Creaded index: " + index.getName());
            }
            for (String index : event.indicesCreated()) {
                logger.warn("Deleted index: " + index);
            }
        });
        return Collections.emptyList();
    }

    @Override
    public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
        List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> list = new LinkedList<>();
        list.add(new ActionHandler<>(CreateModelFromSetAction.INSTANCE, TransportCreateModelFromSetAction.class));
        return list;
    }
    @Override
    public List<SearchExtSpec<?>> getSearchExts() {
        return singletonList(
                new SearchExtSpec<>(LoggingSearchExtBuilder.NAME, LoggingSearchExtBuilder::new, LoggingSearchExtBuilder::parse));
    }
    @Override
    public List<FetchSubPhase> getFetchSubPhases(FetchPhaseConstructionContext context) {
        return singletonList(new LoggingFetchSubPhase());
    }
    @Override
    public List<AggregationSpec> getAggregations() {
        ArrayList<AggregationSpec> r = new ArrayList<>();
        r.add(
                new AggregationSpec(
                        TimeLimitingAggregationBuilder.NAME,
                        TimeLimitingAggregationBuilder::new,
                        TimeLimitingAggregationBuilder::parse)
                        .addResultReader(InternalTimeLimiting::new)
        );
        return r;
    }

    @Override
    public Settings additionalSettings() {
        final Settings.Builder builder = Settings.builder();

        // Exposes REDIS_URL as a node setting
        builder.put(REDIS_URL.getKey(), config.getRedisUrl());

        logger.warn("finished settings");
        return builder.build();
    }

    /**
     * 不加会报 Unknown settings，说明配置文件中这个setting是有人使用的
     * @return
     */
    public List<Setting<?>> getSettings() {
        return Arrays.asList(REDIS_URL);
    }

    /**
     * https://github.com/anti-social/elasticsearch-rescore-grouping-mixup
     */
    @Override
    public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
        return new PositionRecipScriptEngine();
    }
    /**
     * https://github.com/anti-social/elasticsearch-rescore-grouping-mixup
     * GET /aa/_search
     * {
     *   "explain": true,
     *   "query": {
     *     "function_score": {
     *       "script_score": {
     *         "script": "doc['rank'].value"
     *       }
     *     }
     *   },
     *   "rescore": {
     *     "window_size": 50,
     *     "grouping_mixup": {
     *       "field": "manufacturer",
     *       "decline_script": {
     *           "script": {
     *             "lang": "grouping_mixup_scripts",
     *             "source": "position_recip"
     *           }
     *         }
     *     }
     *   }
     * }
     *
     * https://github.com/BigDataBoutique/elasticsearch-rescore-redis
     * GET aa/_search
     * {
     *   "rescore": {
     *     "window_size": 50,
     *     "redis": {
     *       "key_field": "manufacturer",
     *       "key_prefix": "plugin-"
     *     }
     *   }
     * }
     * elasticsearch.yml增加 -> redisRescore.redisUrl: cache.redis.scsite.net
     */
    @Override
    public List<RescorerSpec<?>> getRescorers() {
        List<RescorerSpec<?>> list = new LinkedList<>();
        list.add(new RescorerSpec<>(
                GroupingMixupRescorerBuilder.NAME,
                GroupingMixupRescorerBuilder::new,
                GroupingMixupRescorerBuilder::fromXContent
        ));
        list.add(new RescorerSpec<>(RedisRescoreBuilder.NAME, RedisRescoreBuilder::new, RedisRescoreBuilder::fromXContent));
        return list;
    }

    /**
     * https://zhuanlan.zhihu.com/p/136934822
     * GET delimited_payload/_search
     * {
     *   "query": {
     *     "payload_term": {
     *       "text": "brown"
     *     }
     *   }
     * }
     */
    @Override
    public List<QuerySpec<?>> getQueries() {
        return Collections.singletonList(
                new QuerySpec<>(PayloadSearchQueryBuilder.NAME, PayloadSearchQueryBuilder::new, PayloadSearchQueryBuilder::fromXContent)
        );
    }

    /**
     * PUT /aa
     * {
     *   "mappings": {
     *     "a" : {
     *       "properties": {
     *         "title":{
     *             "analyzer": "keyword",
     *           	"store": true,
     *           	"term_vector": "with_positions_offsets",
     *           	"type": "preanalyzed"
     *         },
     *         "author": {
     *           "type":"text",
     *           "analyzer" : "whitespace"
     *                            }
     *       }
     *     }
     *   }
     * }
     * PUT /aa/a/1
     * {
     * 	"author": "Anna Sewell",
     * 	"title": "{\"v\":\"1\",\"str\":\"Black Beauty ran past the bloody barn.\",\"tokens\":[{\"t\":\"Black\",\"s\":0,\"e\":5,\"i\":1},{\"t\":\"hero\",\"s\":0,\"e\":12,\"i\":0},{\"t\":\"Beauty\",\"s\":6,\"e\":12,\"i\":1},{\"t\":\"ran\",\"s\":13,\"e\":16,\"i\":1},{\"t\":\"past\",\"s\":17,\"e\":21,\"i\":1},{\"t\":\"the\",\"s\":22,\"e\":25,\"i\":1},{\"t\":\"bloody\",\"s\":26,\"e\":32,\"i\":1},{\"t\":\"NP\",\"s\":26,\"e\":37,\"i\":0},{\"t\":\"NNP\",\"s\":26,\"e\":37,\"i\":0},{\"t\":\"barn\",\"s\":33,\"e\":37,\"i\":1},{\"t\":\".\",\"s\":37,\"e\":38,\"i\":1}]}",
     * 	"year": 1877
     * }
     * GET /aa/_search
     * {
     *   "query": {
     *     "match": {
     *       "title": "NNP"
     *     }
     *   },
     *   "highlight": {
     *     "fields": {
     *       "title": {}
     *     }
     *   }
     * }
     * @return
     */
    @Override
    public Map<String, Mapper.TypeParser> getMappers() {
        return Collections.singletonMap("preanalyzed", new PreAnalyzedMapper.TypeParser());
    }
}
